Modern computing systems are capable of running a large variety of software applications and platforms. Developers write these software applications in a programming language. For example, developers can write an application using C, C++, Objective-C, or Java. The developer can then compile the application into object code or machine code that the processor can directly execute.
While running, the processor can interact with other components of the computer system. For example, the processor may store data in system memory. The processor can access the computers system memory, graphics memory, cache memory, or connected storage devices such as hard disks. Further, the processor can interact with a variety of input/output (“I/O”) devices. For example, a keyboard or mouse can provide user input, a graphics monitor can display output from the application, and a network interface can allow the application to send and receive data across the network.
An operating system, such as Microsoft Windows, Linux, or Max OS X, runs on the base hardware providing a platform for additional applications. A developer can write an application to run on a specific operating system. The application can make use of software libraries and functionality built into an operating system to provide access to the underlying hardware components.
Virtualization technologies have become widely used for sharing limited computer hardware resources. In a hardware virtualization environment, sometimes also referred to as a platform virtualization environment, one or more virtual machines can simulate a host computer with its own operating system. In particular, software executed on these virtual machines can be separated from the underlying hardware resources. As a result, multiple virtual machines running on a single host computer can each have its own operating system. For example, a computer that is running Microsoft Windows operating system may host multiple virtual machines: one virtual machine that simulates a computer running a Linux operating system, based on which Linux-based software can be executed; a second virtual machine simulating a computer running a OS X, on which OS X based software can be executed; and additional virtual machines each running its own operating system.
Whether a processor is physical or virtual, the processor can execute a computer program by following each instruction in the compiled software application. When an application is compiled, the program is converted into a sequence of instructions readable by a specific type of processer. The process can execute an instruction cycle for each instruction by loading the instruction into the processor, determining what action the instruction requires, and taking that action. The processor can repeat this cycle for each instruction in the program until the application finishes running.
The instructions executed by the processor can directly affect other aspects of the computing environment. Instructions can write data to memory. Additionally, program instructions can request data from other devices connected to the computer. For example, these devices, generically referred to as input/output devices or I/O devices, can include, a hard disk, keyboard, mouse, or optical drive. The processor can write data to and read data from an I/O device. I/O devices can notify the processor that they have data for the processor. This notification is called an interrupt request (“IRQ”). When the processor receives an IRQ from an I/O device, the processor can interrupt its current execution, read the data from the device, and continue its execution. Additionally, a processor may write data to an I/O device or initiate interaction with an I/O device. Often the processor will request data from the I/O device, wait until the I/O device finishes, and then read the data from the I/O device. Because interacting with an I/O device can sometimes take much longer than other processor tasks, modern processers can use direct memory access (“DMA”). Using DMA, a processor can initiate interaction with an I/O device, can continue executing other instructions while waiting for the I/O device to respond, and can handle the IRQ from the device informing the processor that the request is complete.
The order and result of processor instructions are deterministic. Using the same input data and the same instruction, the same result will occur. This determinism can allow for predictable results when executing application instructions. I/O data, however, is non-deterministic. Reading from an I/O device or the result of an IRQ will not always produce the same result as a previous read or IRQ. Because of their non-deterministic nature, I/O operations are inherently unpredictable. This unpredictability can cause difficulty when trying to determine the processors exact execution history.
There are many practical uses for investigating the past operation and execution of a processor. For example, developers often need to re-run portions of software applications in order to find and fix bugs and errors in the application. Unfortunately, the non-deterministic nature of I/O renders the ability to precisely replay the execution of an application impossible. Because the I/O operations are non-deterministic, a developer cannot guarantee that running the same instructions with the same input and I/O operations will produce the same error or result. Developers cannot guarantee the exact same execution path when rerunning an application even if the deterministic aspects of the execution remain constant. As a result, debugging specific application failures or errors can be difficult and imprecise.